Previous slide Next slide Toggle fullscreen Open presenter view
Системное программирование
Тема 7. Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
План лекции
1. Понятие динамически подключаемой библиотеки
2. Структура DLL-библиотеки
3. Создание DLL-библиотеки
4. Проецирование файлов на виртуальное адресное пространство
5. Разработка и использование динамически загружаемых модулей
6. Обмен данными между процессами с использованием динамически загружаемых модулей и разделяемых сегментов памяти
7. Создание многозадачных комплексов
8. Перехват системных вызовов (hooking)
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
1. Динамически подключаемые библиотеки
1.1. Понятие DLL
DLL (Dynamic Link Library) — библиотека, загружаемая во время выполнения.
Преимущества:
Экономия памяти (одна копия для всех процессов)
Модульность приложений
Обновление без перекомпиляции
Поддержка плагинов
Расширяемость
Сравнение:
Характеристика
Статическая
Динамическая
Размер EXE
Большой
Меньший
Загрузка
При запуске
При запуске или по требованию
Обновление
Перекомпиляция
Замена DLL
Использование памяти
Копия на процесс
Разделяемая
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
1.2. Типы связывания
Неявное (Implicit) связывание:
#pragma comment(lib, "mylib.lib" )
void MyFunction () ;
MyFunction();
Явное (Explicit) связывание:
HMODULE hModule = LoadLibrary(L"mylib.dll" );
FARPROC proc = GetProcAddress(hModule, "MyFunction" );
((void (*)())proc)();
FreeLibrary(hModule);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
2. Структура DLL
2.1. Экспорт функций
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
MYLIB_API int Add (int a, int b) ;
MYLIB_API void ProcessData (const char * data) ;
class MYLIB_API MyClass {
public:
void DoSomething () ;
};
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
2.2. Файл определения модуля (.def)
; mylib.def
LIBRARY MYLIB
EXPORTS
Add @1
Subtract @2
ProcessData @3 NONAME
; Экспорт по порядковому номеру
Multiply @4
; Экспорт с псевдонимом
Div = Divide @5
Преимущества .def файла:
Экспорт по порядковому номеру (быстрее)
Скрытие имен функций (NONAME)
Псевдонимы функций
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
3. Создание DLL
3.1. Точка входа DLL
#include <windows.h>
BOOL APIENTRY DllMain (
HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
break ;
case DLL_THREAD_ATTACH:
break ;
case DLL_THREAD_DETACH:
break ;
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
3.2. Полный пример DLL
#ifdef MATHLIB_EXPORTS
#define MATHLIB_API __declspec(dllexport)
#else
#define MATHLIB_API __declspec(dllimport)
#endif
extern "C" {
MATHLIB_API double CalculateCircleArea (double radius) ;
MATHLIB_API double CalculateRectangleArea (double w, double h) ;
}
#include "mathlib.h"
#include <cmath>
MATHLIB_API double CalculateCircleArea (double radius) {
return M_PI * radius * radius;
}
MATHLIB_API double CalculateRectangleArea (double w, double h) {
return w * h;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
4. Проецирование файлов
4.1. Memory-Mapped Files
Проецирование файла — отображение файла на адресное пространство процесса.
Преимущества:
Эффективная работа с большими файлами
Разделение данных между процессами
Отсутствие явного чтения/записи
Кэширование ОС
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
4.2. Создание проекции в Windows
HANDLE hFile = CreateFile(
L"data.bin" ,
GENERIC_READ | GENERIC_WRITE,
0 , NULL , OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL
);
HANDLE hMapping = CreateFileMapping(
hFile, NULL ,
PAGE_READWRITE,
0 , 1024 * 1024 ,
L"MyMapping"
);
LPVOID pView = MapViewOfFile(
hMapping, FILE_MAP_ALL_ACCESS,
0 , 0 , 0
);
strcpy ((char *)pView, "Hello, Memory Mapping!" );
UnmapViewOfFile(pView);
CloseHandle(hMapping);
CloseHandle(hFile);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
4.3. Проецирование в POSIX
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int fd = open("data.bin" , O_RDWR | O_CREAT, 0666 );
ftruncate(fd, 1024 * 1024 );
void * addr = mmap(
NULL ,
1024 * 1024 ,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0
);
sprintf ((char *)addr, "Hello, mmap!" );
munmap(addr, 1024 * 1024 );
close(fd);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
5. Использование DLL
5.1. Неявное связывание
#pragma comment(lib, "mathlib.lib" )
extern "C" __declspec(dllimport)
double CalculateCircleArea (double radius) ;
int main () {
double area = CalculateCircleArea(5.0 );
printf ("Area: %f\n" , area);
return 0 ;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
5.2. Явное связывание
#include <windows.h>
#include <stdio.h>
typedef double (*CALC_CIRCLE_AREA) (double ) ;
int main () {
HMODULE hModule = LoadLibrary(L"mathlib.dll" );
if (!hModule) {
printf ("Failed to load DLL\n" );
return 1 ;
}
CALC_CIRCLE_AREA calcArea = (CALC_CIRCLE_AREA)
GetProcAddress(hModule, "CalculateCircleArea" );
if (!calcArea) {
printf ("Failed to get function\n" );
FreeLibrary(hModule);
return 1 ;
}
double area = calcArea(5.0 );
printf ("Area: %f\n" , area);
FreeLibrary(hModule);
return 0 ;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
6. Обмен данными между процессами
6.1. Разделяемая память
Windows:
HANDLE hMap = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL , PAGE_READWRITE,
0 , 4096 , L"SharedMemory"
);
void * pBuf = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0 , 0 , 4096 );
strcpy ((char *)pBuf, "Hello from Process 1" );
HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"SharedMemory" );
void * pBuf = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0 , 0 , 0 );
printf ("Received: %s\n" , (char *)pBuf);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
6.2. Разделяемая память в POSIX
int fd = shm_open("/myshm" , O_CREAT | O_RDWR, 0666 );
ftruncate(fd, 4096 );
void * addr = mmap(NULL , 4096 , PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0 );
sprintf ((char *)addr, "Hello from Process 1" );
int fd = shm_open("/myshm" , O_RDWR, 0666 );
void * addr = mmap(NULL , 4096 , PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0 );
printf ("Received: %s\n" , (char *)addr);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
6.3. Синхронизация доступа
Проблема: Несколько процессов одновременно изменяют данные.
Решение — именованные мьютексы:
HANDLE hMutex = CreateMutex(NULL , FALSE, L"SharedMutex" );
WaitForSingleObject(hMutex, INFINITE);
ReleaseMutex(hMutex);
sem_t * sem = sem_open("/mysem" , O_CREAT, 0666 , 1 );
sem_wait(sem);
sem_post(sem);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
7. Создание многозадачных комплексов
7.1. Архитектура многозадачного приложения
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
7.2. Паттерн "Мастер-Рабочие"
void MasterProcess () {
HANDLE hShm = CreateFileMapping(...);
void * pData = MapViewOfFile(...);
for (int i = 0 ; i < numWorkers; i++) {
STARTUPINFO si = {sizeof (si)};
PROCESS_INFORMATION pi;
wchar_t cmdLine[256 ];
swprintf(cmdLine, L"worker.exe %d" , i);
CreateProcess(NULL , cmdLine, ...);
}
for (int i = 0 ; i < numTasks; i++) {
}
WaitForMultipleObjects(numWorkers, hWorkers, TRUE, INFINITE);
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
7.3. Паттерн "Производитель-Потребитель"
void Producer () {
while (hasData) {
sem_wait(&empty);
sem_wait(&mutex);
buffer[in] = produce();
in = (in + 1 ) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&full);
}
}
void Consumer () {
while (running) {
sem_wait(&full);
sem_wait(&mutex);
item = buffer[out];
out = (out + 1 ) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&empty);
consume(item);
}
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8. Перехват системных вызовов
8.1. Понятие hooking (перехвата)
Hooking — техника перехвата вызовов функций для модификации поведения программы.
Применение:
Отладка и мониторинг
Модификация поведения
Антивирусное ПО
Инструменты тестирования
Реверс-инжиниринг
Типы hooking:
Тип
Описание
Сложность
IAT Hooking
Перехват через таблицу импорта
Низкая
Inline Hooking
Перезапись кода функции
Средняя
VTable Hooking
Перехват виртуальных методов
Средняя
DLL Injection
Внедрение кода в процесс
Средняя
Kernel Hooking
Перехват на уровне ядра
Высокая
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8.2. IAT Hooking (Windows)
Import Address Table — таблица адресов импортируемых функций.
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Пример IAT Hooking:
#include <windows.h>
#include <iostream>
typedef int (WINAPI* MessageBoxAPtr) (HWND, LPCSTR, LPCSTR, UINT) ;
MessageBoxAPtr OriginalMessageBoxA = nullptr ;
int WINAPI HookedMessageBoxA (HWND hWnd, LPCSTR lpText,
LPCSTR lpCaption, UINT uType) {
std::cout << "MessageBoxA called: " << lpText << std::endl;
return OriginalMessageBoxA (hWnd, "Hooked!" , lpCaption, uType);
}
void HookIAT (const char * moduleName, const char * funcName, void * newFunc) {
HMODULE hModule = GetModuleHandleA (NULL );
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)hModule;
IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)hModule +
dosHeader->e_lfanew);
IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)(
(BYTE*)hModule + ntHeaders->OptionalHeader.DataDirectory[
IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (importDesc->Name) {
const char * dllName = (const char *)((BYTE*)hModule + importDesc->Name);
if (_stricmp(dllName, moduleName) == 0 ) {
IMAGE_THUNK_DATA* thunk = (IMAGE_THUNK_DATA*)(
(BYTE*)hModule + importDesc->FirstThunk);
while (thunk->u1.Function) {
FARPROC* funcPtr = (FARPROC*)&thunk->u1.Function;
if (*funcPtr == GetProcAddress(
GetModuleHandleA(moduleName), funcName)) {
DWORD oldProtect;
VirtualProtect(funcPtr, sizeof (void *),
PAGE_EXECUTE_READWRITE, &oldProtect);
OriginalMessageBoxA = (MessageBoxAPtr)*funcPtr;
*funcPtr = (FARPROC)newFunc;
VirtualProtect(funcPtr, sizeof (void *),
oldProtect, &oldProtect);
}
thunk++;
}
}
importDesc++;
}
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8.3. LD_PRELOAD (Linux)
LD_PRELOAD — переменная окружения для загрузки библиотек до других.
Пример перехвата malloc:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
static void * (*real_malloc)(size_t ) = NULL ;
void * malloc (size_t size) {
if (!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc" );
}
printf ("malloc(%zu) called\n" , size);
return real_malloc(size);
}
void free (void * ptr) {
static void (*real_free) (void *) = NULL ;
if (!real_free) {
real_free = dlsym(RTLD_NEXT, "free" );
}
printf ("free(%p) called\n" , ptr);
real_free(ptr);
}
Компиляция и использование:
gcc -shared -fPIC -o hook.so hook.c -ldl
LD_PRELOAD=./hook.so ./program
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8.4. ptrace (Linux)
ptrace — системный вызов для отладки и трассировки процессов.
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/user.h>
int main () {
pid_t child = fork();
if (child == 0 ) {
ptrace(PTRACE_TRACEME, 0 , NULL , NULL );
execl("./target" , "target" , NULL );
} else {
int status;
waitpid(child, &status, 0 );
while (WIFSTOPPED(status)) {
struct user_regs_struct regs ;
ptrace(PTRACE_GETREGS, child, NULL , ®s);
printf ("Syscall: %lld\n" , regs.orig_rax);
ptrace(PTRACE_SYSCALL, child, NULL , NULL );
waitpid(child, &status, 0 );
}
}
return 0 ;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8.5. Microsoft Detours
Detours — библиотека Microsoft для hooking на уровне кода.
#include <detours.h>
#include <windows.h>
static int (WINAPI* TrueMessageBoxA) (HWND, LPCSTR, LPCSTR, UINT) = MessageBoxA;
int WINAPI HookedMessageBoxA (HWND hWnd, LPCSTR lpText,
LPCSTR lpCaption, UINT uType) {
return TrueMessageBoxA (hWnd, "Detoured!" , lpCaption, uType);
}
int main () {
DetourTransactionBegin ();
DetourUpdateThread (GetCurrentThread ());
DetourAttach (&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
DetourTransactionCommit ();
MessageBoxA (NULL , "Test" , "Test" , MB_OK);
DetourTransactionBegin ();
DetourUpdateThread (GetCurrentThread ());
DetourDetach (&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
DetourTransactionCommit ();
return 0 ;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8.6. eBPF (Linux)
eBPF (Extended Berkeley Packet Filter) — технология выполнения песочницы программ в ядре Linux без необходимости написания модулей ядра.
Архитектура eBPF:
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Ключевые компоненты:
Verifier — статический анализатор, проверяющий безопасность программы (отсутствие бесконечных циклов, доступ только к разрешённой памяти, ограниченное число инструкций)
JIT-компиляция — преобразование BPF-байткода в нативный машинный код для максимальной производительности
BPF Maps — структуры данных для обмена между ядром и пользовательским пространством (hash, array, ring buffer, per-CPU)
Helper functions — набор разрешённых функций ядра, которые eBPF-программа может вызывать (bpf_get_current_pid_tgid, bpf_probe_read, bpf_trace_printk и др.)
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Типы eBPF-программ:
Тип
Точка прикрепления
Применение
kprobes/kretprobes
Точка входа/выхода функции ядра
Отладка, мониторинг
tracepoints
Статические точки трассировки ядра
Мониторинг событий
XDP (eXpress Data Path)
Драйвер сетевой карты
Высокоскоростная обработка пакетов
tc (traffic control)
Сетевой стек Linux
Фильтрация, QoS
perf_events
Счётчики производительности
Профилирование
uprobe/uretprobe
Точка входа/выхода функции пользователя
Трассировка приложений
cgroupskb
Контрольная группа
Мониторинг контейнеров
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Пример: трассировка open() с помощью bpftrace (однострочник):
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s: %s\n", comm, str(args->filename)); }'
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Пример: BPF-программа с BCC (Python + BPF C):
from bcc import BPF
bpf_text = """
#include <uapi/linux/ptrace.h>
struct data_t {
u32 pid;
char comm[16];
char fname[256];
};
BPF_PERF_OUTPUT(events);
int trace_open(struct pt_regs *ctx, const char __user *filename) {
struct data_t data = {};
u32 pid = bpf_get_current_pid_tgid() >> 32;
data.pid = pid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
bpf_probe_read_user_str(&data.fname, sizeof(data.fname), filename);
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event="do_sys_openat2" , fn_name="trace_open" )
def print_event(cpu, data, size):
event = b["events" ].event(data)
print(f"{event.comm.decode()} [{event.pid}] {event.fname.decode()}" )
b["events" ].open_perf_buffer(print_event)
while True:
b.perf_buffer_poll()
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Инструменты на базе eBPF:
Инструмент
Назначение
bpftrace
Высокоуровневый язык трассировки (однострочники, скрипты)
BCC (BPF Compiler Collection)
Python/C-фреймворк для eBPF-инструментов
Cilium
CNI-плагин для Kubernetes с eBPF-сетевой политикой
Falco
Обнаружение аномалий в рантайме (безопасность)
bpftrace-tools
Набор готовых инструментов (execsnoop, opensnoop, biosnoop)
Katran
L4-балансировщик нагрузки от Facebook
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Пример: tracing open() syscalls (libbpf, eBPF C):
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
struct event {
u32 pid;
char comm[16 ];
char fname[256 ];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24 );
} events SEC (".maps" ) ;
SEC("tracepoint/syscalls/sys_enter_openat" )
int trace_openat (struct trace_event_raw_sys_enter* ctx) {
struct event * e ;
e = bpf_ringbuf_reserve(&events, sizeof (*e), 0 );
if (!e) return 0 ;
e->pid = bpf_get_current_pid_tgid() >> 32 ;
bpf_get_current_comm(&e->comm, sizeof (e->comm));
bpf_probe_read_user_str(&e->fname, sizeof (e->fname),
(const char *)ctx->args[1 ]);
bpf_ringbuf_submit(e, 0 );
return 0 ;
}
char LICENSE[] SEC("license" ) = "GPL" ;
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Сравнение методов мониторинга/перехвата:
Характеристика
eBPF
ptrace
LD_PRELOAD
Уровень
Ядро
Ядро (system call)
Пользовательский
Производительность
Очень высокая
Низкая (context switch)
Высокая
Область видимости
Все процессы
Один процесс
Один процесс
Требование root
Да (для большинства)
Нет (свой процесс)
Нет
Безопасность
Verifier, sandbox
SIGSTOP/SIGCONT
Обход через статическую линковку
Гибкость
Ограничена verifier
Полная
Полная
Изменение данных
Ограничено
Да
Да
Использование
Мониторинг, сеть, безопасность
Отладчики, strace
Перехват библиотечных функций
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
8.7. Сравнение методов hooking
Метод
Платформа
Уровень
Обнаруживаемость
Сложность
IAT Hooking
Windows
User
Высокая
Низкая
Inline Hooking
Любая
User
Средняя
Средняя
LD_PRELOAD
Linux
User
Высокая
Низкая
ptrace
Linux
User
Высокая
Средняя
Detours
Windows
User
Низкая
Низкая
eBPF
Linux
Kernel
Низкая
Высокая
Рекомендации:
Отладка/мониторинг : LD_PRELOAD, ptrace, eBPF
Модификация ПО : Detours, IAT Hooking
Безопасность : eBPF, kernel modules
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Резюме
Ключевые моменты лекции:
DLL — динамически подключаемые библиотеки
Экспорт/Импорт — механизмы использования DLL
Проецирование файлов — эффективная работа с памятью
Разделяемая память — IPC через общую память
Синхронизация — защита разделяемых данных
Многозадачность — паттерны распределения работы
Hooking — перехват системных вызовов
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Вопросы для самопроверки
Чем отличается неявное связывание от явного?
Какие функции выполняет DllMain?
Что такое проецирование файла?
Как организовать обмен данными между процессами?
Какие паттерны многозадачности существуют?
Как синхронизировать доступ к разделяемой памяти?
Что такое IAT Hooking?
Как работает LD_PRELOAD?
Что такое eBPF и для чего он используется?
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Практические задания
Самостоятельная работа (6 часов):
Создать простую DLL с экспортируемыми функциями
Написать программу с явной загрузкой DLL
Организовать разделяемую память между процессами
Исследовать LD_PRELOAD для перехвата функций
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование
Рекомендуемая литература
Основная:
Таненбаум, Э. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2021.
Kerrisk, M. The Linux Programming Interface. — No Starch Press, 2010.
Дополнительная:
MSDN: Dynamic-Link Libraries, File Mapping, Detours
man pages: dlopen(3), mmap(2), shm_overview(7), ptrace(2)
eBPF Documentation: https://ebpf.io/
Microsoft Detours: https://github.com/microsoft/Detours
Механизмы управления виртуальной и динамически распределяемой памятью
Обозначить 8 пунктов плана, дать ориентир по времени (~90 мин). Упомянуть, что тема связана с предыдущей лекцией по памяти и подготовит к лабораторным работам по IPC.
Спросить студентов, какие DLL они знают (kernel32.dll, ntdll.dll). Обратить внимание на сравнительную таблицу — попросить привести примеры из практики, когда перекомпиляция при обновлении библиотеки была бы проблемой.
Ключевое различие: при неявном линкер сам подтягивает DLL при запуске, при явном — программист контролирует загрузку вручную. Спросить, когда какой подход предпочтительнее (ответ: явное — для плагинов и опциональных модулей). Показать аналог в Linux: dlopen/dlsym.
Макрос MYLIB_EXPORTS определяется при сборке самой DLL (через /D в командной строке компилятора). Важно: при экспорте функций C++ происходит декорирование имён (name mangling) — поэтому для совместимости используют extern "C".
.def файл — устаревший, но всё ещё полезный механизм. Обратить внимание на экспорт по порядковому номеру (@1, @2) — это быстрее, чем поиск по имени, но менее безопасно при обновлении. Псевдонимы (Div = Divide) полезны для переименования без изменения клиентского кода.
DllMain вызывается для каждого процесса и потока — подчеркнуть, что нельзя делать в DllMain: вызывать LoadLibrary, ждать других потоков, использовать синхронизацию (возможен дедлок). Частая ошибка — тяжёлая инициализация в DLL_PROCESS_ATTACH.
Показать полный цикл: заголовочный файл, реализация, сборка DLL через cl /LD. extern "C" критичен для избежания name mangling. Предложить студентам поэкспериментировать с dumpbin /exports для просмотра экспортируемых функций.
Связать с темой виртуальной памяти: проецирование — это создание отображения виртуальных страниц на физические страницы, содержащие данные файла. Задать вопрос: что произойдёт при записи в проекцию с PAGE_READONLY? Спросить, где ещё используется mmap (ответ: загрузка executables, shared libraries).
Разобрать каждый шаг по порядку. Важно: CreateFileMapping с hFile=INVALID_HANDLE_VALUE создаёт анонимную проекцию (shared memory). Напомнить про необходимость CloseHandle для всех объектов. Показать, как Process Explorer показывает секции (mapped views).
Сравнить с Windows-версией: mmap вместо CreateFileMapping+MapViewOfFile, shm_open для анонимной разделяемой памяти. MAP_SHARED vs MAP_PRIVATE — ключевое различие: при MAP_PRIVATE изменения не видны другим процессам (copy-on-write). Напомнить про необходимость msync() перед munmap для гарантии записи на диск.
Простой пример — переход от явного к неявному связыванию. Показать, что .lib файл генерируется при сборке DLL. Упомянуть проблему «DLL hell» — что бывает, когда версия DLL не совпадает (решение: side-by-side assemblies, manifests).
Важно показать typedef для указателя на функцию и проверку ошибок после каждого вызова. Спросить: когда лучше использовать явное связывание? (плагины, опциональный функционал, когда DLL может отсутствовать).
INVALID_HANDLE_VALUE — ключевая деталь: означает, что проекция опирается на файл подкачки, а не на реальный файл. Создание vs открытие: CreateFileMapping — первый процесс создаёт, OpenFileMapping — остальные подключаются. Обратить внимание на необходимость одинакового размера.
shm_open — создаёт файл в /dev/shm (tmpfs). Показать через ls /dev/shm. Обратить внимание, что shm_unlink удаляет имя, но память остаётся, пока не закроются все дескрипторы. Спросить: чем отличается shm_open от обычного open + mmap?
Это критический слайд — связать с лекцией по синхронизации. Задать вопрос: что будет, если два процесса одновременно пишут в разделяемую память без синхронизации? (data race, неопределённое поведение). Именованные мьютексы/семафоры нужны именно потому, что обычные мьютексы работают только внутри одного процесса.
Визуализировать архитектуру на доске: главный процесс как координатор, рабочие как исполнители. Спросить: какие реальные системы используют такую архитектуру? (ответ: компиляторы с параллельной компиляцией, веб-серверы worker processes, база данных PostgreSQL).
Разобрать код: CreateProcess для запуска рабочих, разделяемая память для передачи задач, WaitForMultipleObjects для ожидания завершения. Спросить: в чём разница между процессами и потоками для этой задачи? (процессы изолированы, но дороже в создании; потоки делят адресное пространство, проще обмен данными).
Классический паттерн — разбор на доске с буфером и указателями in/out. Важно: порядок sem_wait — сначала empty/full, потом mutex (иначе дедлок!). Спросить: как изменится код, если буфер кольцевой и одного элемента?
Большой слайд — выделить на него больше времени (~15 мин). Hooking — двоякая технология: используется как в легитимных целях (антивирусы, отладчики), так и в вредоносных (rootkits, кейлоггеры). Спросить: как антивирус обнаруживает IAT hooking?
Быстро пробежаться по 7 пунктам. Предложить студентам выбрать 2-3 ключевых темы для самостоятельного углубления.
Выбрать 2-3 вопроса для обсуждения в аудитории. Вопросы 4 и 6 особенно важны — они связывают тему с предыдущими лекциями по IPC и синхронизации.
Задания 1-2 выполнить на Windows (Visual Studio), задание 3 — на Linux (POSIX shared memory), задание 4 — на Linux (LD_PRELOAD). Рекомендовать начать с LD_PRELOAD — это самый наглядный и быстрый результат.
Таненбаум — глава по управлению памятью. Kerrisk — главы 49 (разделяемая память), 14 (mmap). Для hooking: man ptrace(2), документация eBPF на ebpf.io.